所有的範例程式碼都可以從 Github repo 獲得。第一章的程式碼則是 Chapter 01。範例資料來源自 the Complex Systems Research Group at the Medical University of Vienna,請使用第一章 repo 中附上的 script/download-data.sh
下載。
資料下載完後,就可以開始嘗試用 Incanter 模組進行基本操作了。Incanter 是嘗試將 R 的 data.frame 概念及操作、建構在 data.frame 上的工具移植到 Clojure 的函式庫。
在專案中使用 Incanter,請在 project.clj
中引入:
:dependencies [[incanter/incanter-core "1.5.5"]
[incanter/incanter-stats "1.5.5"]]
Incanter 下有很多模組。引用會用到的即可。讓我們來讀取資料:
incanter-io
incanter-excel
dataset
這個函數轉為 Incanter 可讀取的狀態(ns cljds.ch1.data
(:require [clojure.java.io :as io]
[incanter.core :as i]
[incanter.excel :as xls]))
(defmulti load-data identity)
(defmethod load-data :uk [_]
(-> (io/resource "UK2010.xls")
(str)
(xls/read-xls)))
這邊使用 multimethod 的方式,定義了一個可以方便取用 UK2010 數據的語句 (load-data :uk)
。接著我們使用 incanter.core
的功能:col-name
,這個函數可以返回 data.frame 中的各 column 的名字。
(defn ex-1-1 []
(i/col-name (load-data :uk)))
ex-
開頭的函數可以用 lein 直接執行,所以我們下:lein run -e 1.1
,即可看到輸出!
資料科學小訣竅:處理前須了解每個 column 的意義及資料類型
(defn ex-1-2 []
(i/$ "Election Year" (load-data :uk)))
; (2010.0 2010.0 2010.0 2010.0 2010.0 ... 2010.0 2010.0 nil)
(defn ex-1-3 []
(->> (load-data :uk)
(i/$ "Election Year")
(distinct)))
; (2010 nil)
第一個函數輸出所有的值。第二個函數則用 distinct
將值分開。可以發現,資料中存在有某些行,它們的 Election Year
值是空(nil
)的。有多少個行的資料呢?
(defn ex-1-4 []
(->> (load-data :uk)
(i/$ "Election Year")
(frequencies)))
; {2010.0 650 nil 1}
使用 frequencies
函數來看有多少筆 nil
。可以看到,是一筆。
資料科學小訣竅:有人說資料科學家的 80% 工作內容都是資料清理
(-> (load-data :uk)
(i/query-dataset {"Election Year" {:$eq nil}}))
query-dataset
是反向選擇資料。若要跟 SQL 中的 WHERE
一樣是篩選出需要的資料,則是使用 i/$w
。
Clojure | Math | English |
---|---|---|
:$gt |
> | greater than |
:$lt |
< | less than |
:$gte |
>= | greater than or equal to |
:$lte |
<= | less than or equal to |
:$eq |
== | equal to |
:$ne |
!= | not equal to |
:$in |
positive membership | |
:$nin |
negative membership | |
:$fn |
a self defined comparison function |
接著讓我們通過 map to key 的方式多了解有問題的行:(把那一整行以 column name 為 key,實際值為 value 的方式轉為 map。多於一行的話,map 會存在一個 vector 中。
(defn ex-1-5 []
(->> (load-data :uk)
(i/$where {"Election Year" {:$eq nil}})
(i/to-map)))
; {:ILEU nil, :TUSC nil, :Vote nil ... :IVH nil, :FFR nil}
結果這一整行都是空的!所以我們可以去掉這行。下列程式產生清理過的資料:
(->> (load-data :uk)
(i/$where {"Election Year" {:$ne nil}}))
經過檢測,nil
資料已被除去。因此,我們可以重新寫一開始的 load-data
,讓程式能夠提供我們正確的資料:
(defmethod load-data :uk-scrubbed [_]
(->> (load-data :uk)
(i/$where {"Election Year" {:$ne nil}})))
描述統計不做資料的意義的猜測(相對於推論統計 inferential statistics),主要目的是展示資料的各種分布屬性。
(defn ex-1-6 []
(->> (load-data :uk-scrubbed)
(i/$ "Electorate")
(count)))
count
是最基本的計次函數!
Name | Math Symbol | Clojure |
---|---|---|
Sigma | Σ | (reduce + xs) |
Pi | Π | (reduce * xs) |
所以,以平均為例,函數如下
(defn mean [xs]
(/ (reduce + xs)
(count xs)))
(defn ex-1-7 []
(->> (load-data :uk-scrubbed)
(i/$ "Electorate")
(mean))
; 70149.94
不過,Incanter 其實已經提供了平均函數 mean
可以直接調用了。這個函數存在於 incanter.stats
這個 namespace 中,因為接下來會用到,所以請添加到我們的 ns
中吧!